#include <vector>
#include <algorithm>
#include "AgentGenome.h"
#include "KnockingStrategy.h"
#include "KnockingStrategyCollection.h"
#include "DrawingStrategy.h"
#include "DrawingStrategyCollection.h"
#include "DiscardStrategy.h"
#include "DiscardStrategyCollection.h"
#include "strategies/MaxScoreDiscardStrategy.h"
#include "Query.h"

using namespace std;

StrategyCodec AgentGenome::codec;
int AgentGenome::predefinedCounter = 0;

void AgentGenome::Init(GAGenome& genomeRef) {
    AgentGenome& agentRef = dynamic_cast<AgentGenome&>(genomeRef);
    for (int i = KNOCKING; i <= END_DRAW; i++) {
        int ai = (i == END_DRAW) ? DRAWING : i;
        for (int j = 0; j < AVAILABLE_STRATEGIES[ai]; j++) {
            if (predefinedCounter >= predefinedLimit) {
                agentRef.geneArray[i][j] = codec.encode((StrategyType) i, j);
            }
            else {
                // TODO: Initialize to predefined gene
                predefinedCounter++;
            }
        }
    }
    agentRef.initialized = true;
}

// Win ratio evaluator
float AgentGenome::EvaluateWR(GAGenome& genome) {
    AgentGenome& agentGenome = dynamic_cast<AgentGenome&>(genome);
    agentGenome.numEvaluations++;
    return agentGenome.winRatio();
}

// Score increase rate evaluator
float AgentGenome::EvaluateSIR(GAGenome& genome) {
    AgentGenome& agentGenome = dynamic_cast<AgentGenome&>(genome);
    agentGenome.numEvaluations++;
    return agentGenome.scoreIncreaseRate();
}

// Top score average evaluator
float AgentGenome::EvaluateTSA(GAGenome& genome) {
    AgentGenome& agentGenome = dynamic_cast<AgentGenome&>(genome);
    agentGenome.numEvaluations++;
    return agentGenome.topScoreAverage();
}

// Score difference evaluator
float AgentGenome::EvaluateSD(GAGenome& genome) {
    AgentGenome& agentGenome = dynamic_cast<AgentGenome&>(genome);
    agentGenome.numEvaluations++;
    return agentGenome.scoreDiffHistory();
}

// Losing score difference evaluator
float AgentGenome::EvaluateLSDA(GAGenome& genome) {
    AgentGenome& agentGenome = dynamic_cast<AgentGenome&>(genome);
    agentGenome.numEvaluations++;
    return agentGenome.losingScoreDiffAverage();
}

float AgentGenome::Compare(const GAGenome& g1, const GAGenome& g2) {
    const AgentGenome& ag1 = dynamic_cast<const AgentGenome&>(g1);
    const AgentGenome& ag2 = dynamic_cast<const AgentGenome&>(g2);
    return (ag1.winRatio() - ag2.winRatio());
}

int AgentGenome::Mutate(GAGenome& genome, float probability) {
    AgentGenome& agentGenome = dynamic_cast<AgentGenome&>(genome);
    int numMutations = 0;
    if (agentGenome.initialized) {
        int probInt = (int) (100 * probability);
        for (int i = KNOCKING; i <= END_DRAW; i++) {
            int ai = (i == END_DRAW) ? DRAWING : i;
            for (int j = 0; j < AVAILABLE_STRATEGIES[ai]; j++) {
                int toss = Query::RandomChoice(100);
                if (toss < probInt) {
                    Query::debugOut("Genome mutated\n", 1);
                    agentGenome.geneArray[i][j]->mutateWeight();
                    agentGenome.geneArray[i][j]->mutateParameters();
                    agentGenome.numEvaluations = 0;
                    agentGenome.matchesPlayed = 0;
                    agentGenome.matchesWon = 0;
                    agentGenome.scoreAccum = 0;
                    agentGenome.scoreDiffAccum = 0;
                    agentGenome.losingScoreDiffAccum = 0;
                    agentGenome.increaseRateAccum = 0;
                    numMutations++;
                }
                else {
                    Query::debugOut("Didnt mutate\n", 2);
                }
            }
        }
    }
    else {
        Query::debugOut(string("Genome to mutate not initialized\n"), 1);
    }
    return numMutations;
}

int AgentGenome::Cross(const GAGenome& p1, const GAGenome& p2, GAGenome* c1, GAGenome* c2) {
    Query::debugOut("In crossover\n", 1);
    int numCross = 0;
    const AgentGenome& pa1 = dynamic_cast<const AgentGenome&>(p1);
    const AgentGenome& pa2 = dynamic_cast<const AgentGenome&>(p2);
    AgentGenome* ca1 = NULL;
    AgentGenome* ca2 = NULL;
    if (c1 != NULL) {
        ca1 = dynamic_cast<AgentGenome*>(c1);
        ca1->numEvaluations = 0;
        ca1->matchesPlayed = 0;
        ca1->matchesWon = 0;
        ca1->scoreAccum = 0;
        ca1->scoreDiffAccum = 0;
        ca1->losingScoreDiffAccum = 0;
        ca1->increaseRateAccum = 0;
        ca1->initialized = true;
        numCross++;
    }
    if (c2 != NULL) {
        ca2 = dynamic_cast<AgentGenome*>(c2);
        ca2->numEvaluations = 0;
        ca2->matchesPlayed = 0;
        ca2->matchesWon = 0;
        ca2->scoreAccum = 0;
        ca2->scoreDiffAccum = 0;
        ca2->losingScoreDiffAccum = 0;
        ca2->increaseRateAccum = 0;
        ca2->initialized = true;
        numCross++;
    }
    int crossoverPoint = Query::RandomChoice(crossoverRange);
    int startType;
    int startGene;
    if (crossoverPoint < AVAILABLE_KNOCKING) {
        startType = KNOCKING;
        startGene = crossoverPoint;
    }
    if (crossoverPoint < AVAILABLE_KNOCKING + AVAILABLE_DRAWING) {
        startType = DRAWING;
        startGene = crossoverPoint - AVAILABLE_KNOCKING;
    }
    else if (crossoverPoint < AVAILABLE_KNOCKING + AVAILABLE_DRAWING + AVAILABLE_DISCARD) {
        startType = DISCARD;
        startGene = crossoverPoint - AVAILABLE_KNOCKING + AVAILABLE_DRAWING;
    }
    else {
        startType = END_DRAW;
        startGene = crossoverPoint - AVAILABLE_KNOCKING + AVAILABLE_DRAWING + AVAILABLE_DISCARD;
    }
    for (int i = 0; i <= END_DRAW; i++) {
        int ai = (i == END_DRAW) ? DRAWING : i;
        for (int j = 0; j < AVAILABLE_STRATEGIES[ai]; j++) {
            if (i < startType || (i == startType && j < startGene)) {
                if (ca1 != NULL) {
                    AgentGenome::copyGene(*(pa1.geneArray[i][j]), ca1->geneArray[i][j]);
                }
                if (ca2 != NULL) {
                    AgentGenome::copyGene(*(pa2.geneArray[i][j]), ca2->geneArray[i][j]);
                }
            }
            else if (i == startType && j == startGene) {
                if (ca1 != NULL) {
                    ca1->geneArray[i][j] = new StrategyGene(pa1.geneArray[i][j]->getStrategyName(),
                                                            pa1.geneArray[i][j]->getParametersSize()
                    );
                }
                if (ca2 != NULL) {
                    ca2->geneArray[i][j] = new StrategyGene(pa2.geneArray[i][j]->getStrategyName(),
                                                            pa2.geneArray[i][j]->getParametersSize()
                    );
                }
            }
            else {
                if (ca1 != NULL) {
                    AgentGenome::copyGene(*(pa2.geneArray[i][j]), ca1->geneArray[i][j]);
                }
                if (ca2 != NULL) {
                    AgentGenome::copyGene(*(pa1.geneArray[i][j]), ca2->geneArray[i][j]);
                }
            }
        }
    }
    Query::debugOut("Crossover done\n", 1);
    return numCross;
}

Agent31* AgentGenome::buildAgent(int playerOrder) {
    if (!this->initialized) {
        this->initialize();
    }
    // Knocking strategies
    KnockingStrategy* agentKnockingStrategies[AVAILABLE_STRATEGIES[KNOCKING]];
    for (int i = 0; i < AVAILABLE_STRATEGIES[KNOCKING]; i++) {
        agentKnockingStrategies[i] = dynamic_cast<KnockingStrategy*>(codec.decode(KNOCKING, geneArray[KNOCKING][i]));
    }
    // Drawing strategies
    DrawingStrategy* agentDrawingStrategies[AVAILABLE_STRATEGIES[DRAWING]];
    for (int i = 0; i < AVAILABLE_STRATEGIES[DRAWING]; i++) {
        agentDrawingStrategies[i] = dynamic_cast<DrawingStrategy*>(codec.decode(DRAWING, geneArray[DRAWING][i]));
    }
    // Discard strategies
    DiscardStrategy* agentDiscardStrategies[AVAILABLE_STRATEGIES[DISCARD]];
    for (int i = 0; i < AVAILABLE_STRATEGIES[DISCARD]; i++) {
        agentDiscardStrategies[i] = dynamic_cast<DiscardStrategy*>(codec.decode(DISCARD, geneArray[DISCARD][i]));
    }
    // End drawing strategies
    DrawingStrategy* agentEndDrawingStrategies[AVAILABLE_STRATEGIES[DRAWING]];
    for (int i = 0; i < AVAILABLE_STRATEGIES[DRAWING]; i++) {
        agentEndDrawingStrategies[i] = dynamic_cast<DrawingStrategy*>(codec.decode(DRAWING, geneArray[END_DRAW][i]));
    }
    DiscardStrategy* agentDiscardStrategy = new DiscardStrategyCollection(agentDiscardStrategies);
    DrawingStrategy* agentDrawingStrategy = new DrawingStrategyCollection(agentDrawingStrategies);
    agentDrawingStrategy->setDiscardStrategy(agentDiscardStrategy);
    KnockingStrategy* agentKnockingStrategy = new KnockingStrategyCollection(agentKnockingStrategies);
    agentKnockingStrategy->setDrawingStrategy(agentDrawingStrategy);
    DiscardStrategy* agentEndDiscardStrategy = new MaxScoreDiscardStrategy(100);
    DrawingStrategy* agentEndDrawingStrategy = new DrawingStrategyCollection(agentEndDrawingStrategies);
    agentEndDrawingStrategy->setDiscardStrategy(agentEndDiscardStrategy);
    return new Agent31(playerOrder,
                agentKnockingStrategy,
                agentDrawingStrategy,
                agentDiscardStrategy,
                agentEndDrawingStrategy,
                agentEndDiscardStrategy
   );
}

